//==============================================================================
//
//            DText - the text string class in the ofc-library
//
//               Copyright (C) 2003  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2005-02-05 06:25:38 $ $Revision: 1.26 $
//
//==============================================================================

#include "ofc/DText.h"

#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>

#include <ofc/DInt.h>
#include <ofc/DLong.h>
#include <ofc/DDouble.h>


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DDatable.h"
#include "ofc/DTextable.h"
#include "ofc/DComparable.h"
#include "ofc/DSizeable.h"
#include "ofc/DTextReadable.h"
#include "ofc/DTextScannable.h"
#include "ofc/DTextWritable.h"
#include "ofc/DParsable.h"

#include "ofc/DData.h"

// The text string class implements methods for storing and manipulating text strings.

@interface DText : Object <DDatable,DTextable,DComparable,DSizeable,DTextReadable,DTextScannable,DTextWritable,DParsable>
{
@private
  unsigned long  _size;     // the allocated size for the string
  unsigned       _extra;    // the extra amount for sizing
  unsigned long  _length;   // the length of the string
  unsigned long  _pntr;     // the pointer for the DTextReadable,-Writable protocols
  char          *_string;   // the string data
  int            _error;    // the last error (or 0)
}

#endif



@implementation DText


#if _PRIVATE_
  
static long index2offset(DText *string, long index)
{
  if (index < 0)
    index = string->_length + index;

  if (index < 0)
  {
    index = 0;

    WARNING(DW_INVALID_ARG, "index");
  }
  
  if (index > string->_length)
  {
    index = string->_length-1;

    WARNING(DW_INVALID_ARG, "index");
  }

  return index;
}

#endif



//// Constructors

//
// Initialise an empty string
//
// @return the string object
//

- (DText *) init
{
  [super init];
  
  _length = 0;
  _pntr   = 0;
  _size   = 0;
  _string = NULL;
  _error  = 0;

  [self size :0];
    
  return self;
}

//
// Initialise the object with a c-string
//
// @param cstring  the zero terminated ascii string
//
// @return the string object
//

- (DText *) init :(const char *) cstring
{
  [self init];

  [self set :cstring];
  
  return self;
}


//// Copy related methods

//
// Deepen a copy of the string object
//
// @return the string object
//

- deepen
{
  char *orig = _string;
  
  [super deepen];
  
  _string = (char *) objc_malloc(sizeof(char) * _size);
 
  memcpy(_string, orig, _length);
  
  return self;
}


//// Deconstructor

//
// Free the object
//
// @return nothing
//

- free
{
  if (_string != NULL)
  {
    objc_free(_string);
  }

  return [super free];
}


//// DSizeable protocol methods

//
// Set the size (storage space) for the string
//
// @param size   the request size for the string
//
// @return the string object
//

- (DText *) size :(unsigned long) size
{
  if ((size + 1) > _size)
  {
    _size = size + _extra + 1;
    
    if (_string == NULL)
    {
      _string = (char *) objc_malloc(sizeof(char) * _size);
    }
    else
    {
      _string = (char *) objc_realloc(_string, sizeof(char) * _size);
    }
  }

  return self;
}

//
// Set the amount of extra size increase if the size must be extended
//
// @param extra    the amount (def. 0)
//
// @return the object
//

- (DText *) extra :(unsigned) extra
{
  _extra = extra;

  return self;
}


//// Conversion methods

//
// Convert the string to a floating point number
//
// @return the floating point number
//

- (double) toDouble
{
  DDouble *obj = [[DDouble alloc] init];
  double   val;
  char    *str = (char *) [self cstring];
    
  _error = [obj fromString :&str];
  
  val    = [obj get];
  
  [obj free];
  
  return val;
}

//
// Convert the string to a integer number
//
// @return the integer number
//

- (int) toInt
{
  DInt *obj = [[DInt alloc] init];
  int   val;
  char *str = (char *) [self cstring];
  
  _error = [obj fromString :&str];

  val    = [obj get];

  [obj free];
  
  return val;
}

//
// Convert the string to a long number
//
// @return the long number
//

- (long) toLong
{
  DLong *obj = [[DLong alloc] init];
  long   val;
  char  *str = (char *) [self cstring];

  _error = [obj fromString :&str];
  val    = [obj get];

  [obj free];
  
  return val;
}


//// String manipulation

//
// Format the string
//
// @param format   the format string
//
// @return the string object
//

- (DText *) format :(const char *) format,...
{
  va_list ap;
  int     length;
  
  // guess the length = 2 * length format 
  [self size :(2 * strlen(format))];   
  
  while (1)
  {
    va_start(ap, format);
    length = vsnprintf(_string, _size, format, ap);
    va_end(ap);

    if (length == -1)
    {
      // not big enough, try twice as big
      [self size :(2 * _size)];
    }
    else if (length < _size)
    {
      _length = length;

      break;
    }
    else
    {
      // not big enough, try the suggested value
      [self size :length];
    }
  }

  return self;
}

//
// Clear the text string
// 
// @return the object
// 

- (DText *) clear
{
  _length = 0;
  _pntr   = 0;
  
  return self;
}

//
// Set a c-string in the string
//
// @param cstring  the c-string
//
// @return the string object
//

- (DText *) set :(const char *) cstring
{
  _length = (cstring != NULL) ? strlen(cstring) : 0;

  [self size :_length];

  memcpy(_string, cstring, _length);

  return self;
}

//
// Set a substring of a c-string in the object
//
// @param cstring  the c-string
// @param from     the start in the c-string
// @param to       the end in the c-string
//
// @return the object
//

- (DText *) set :(const char *) cstring :(long) from :(long) to
{
  if (cstring != NULL)
  {
    int length = strlen(cstring);
    
    if (from < 0      ) from = 0;
    if (from >= length) from = length-1;
    if (to   < 0      ) to   = 0;
    if (to   >= length) to   = length-1;

    if (to >= from)
    {
      _length = to - from + 1;

      [self size :_length];
      
      memcpy(_string, cstring+from, _length);
    }
  }

  return self;
}

//
// Set number of characters in the string
//
// @param ch        the character to be set
// @param number    the number of characters
// 
// @return the object
// 
- (DText *) set :(char) ch :(long) number
{
  _length = number;
  
  [self size :_length];
  
  memset(_string, ch, _length);
  
  return self;
}

// Set a character at the given index (must fit in string)
//
// @param index    the index in the string
// @param ch       the character to be placed in the string
//
// @return the string object
//

- (DText *) put :(long) index :(char) ch
{
  if (_length > 0)
  {
    index = index2offset(self, index);

    _string[index] = ch;
  }
  
  return self;
}

//
// Get the character at the given index
//
// @param index    the index in the string
//
// @return the character in the string or \0
//

- (char) get :(long) index
{
  if (_length > 0)
  {
    index = index2offset(self, index);
  
    return _string[index];
  }
  else
    return EOS;
}

//
// Delete the character at the given index
//
// @param index    the index in the string
//
// @return the string object
//

- (DText *) delete :(long) index
{
  index = index2offset(self, index);
  
  if (_length > 0)
  {
    memmove(_string+index, _string+index+1, _length-index-1);

    _length--;
  }
  return self;
}

//
// Insert a cstring in a part of the string.
//
// @param from     the start index
// @param to       the end index (to >= (from-1))
// @param cstring  the c-string
//
// @return the string object
//

- (DText *) insert :(long) from :(long) to :(const char *) cstring
{
  from = index2offset(self, from);
  to   = index2offset(self, to  );

  if (to >= (from-1))
  {
    long length = (cstring != NULL) ? strlen(cstring) : 0;

    [self size :(_length + length - (to - from + 1))];
    
    memmove(_string + from + length, _string + to +  1, (_length - to - 1));
    
    memmove(_string + from, cstring, length);

    _length += (length - (to - from + 1));
  }
    
  return self;
}

//
// Insert a number of characters in a part of the string
//
// @param from     the start index
// @param to       the end index (to >= (from-1))
// @param ch       the character to be inserted
// @param number   the number of characters to insert
//
// @return the string object
//

- (DText *) insert :(long) from :(long) to :(char) ch :(long) number
{
  from = index2offset(self, from);
  to   = index2offset(self, to  );

  if (to >= (from-1))
  {
    long length = (number > 0) ? number : 0;

    [self size :(_length + length - (to - from + 1))];
    
    memmove(_string + from + length, _string + to +  1, (_length - to - 1));
    
    memset (_string + from, ch, length);

    _length += (length - (to - from + 1));
  }
    
  return self;
}

//
// Return a substring 
//
// @param from     the start index
// @param to       the end index (to >= from)
//
// @return a new text string object
//

- (DText *) get :(long) from :(long) to
{
  DText *sub = [[DText alloc] init];
  
  from = index2offset(self, from);
  to   = index2offset(self, to  );

  if (to >= from)
  {
    [sub set :[self cstring] :from :to];
  }
      
  return sub;
}

//
// Delete a substring from the string
//
// @param from     the start index
// @param to       the end index (to >= from)
//
// @return the string object
//

- (DText *) delete :(long) from :(long) to
{
  if (_length > 0)
  {
    from = index2offset(self, from);
    to   = index2offset(self, to  );

    if (to >= from)
    {
      memmove(_string+from, _string + to + 1, (_length - to - 1));

      _length -= (to - from + 1);
    }
  }
  return self;
}

//
// Split the string in two: the part before the separator character is
// stored in the returning new string object; the part after the separator
// remains in the string object.
//
// @param separator the character
//
// @return a new text object (or nil if character not found)
//

- (DText *) split :(char) separator
{
  DText *splitof = nil;
  
  if (_length > 0)
  {
    long i = 0;
    
    while ((i < _length) && (_string[i] != separator))
    {
      i++;
    }
    
    if (i < _length)
    {
      splitof = [DText new];
      
      if (i > 0)
      {
        [splitof set :[self cstring] :0 :i-1];
      }
      
      [self delete :0 :i];
    }
      
  }
  
  return splitof;
}

//// String properties methods

//
// Check if the string is empty
//
// @return Is the string empty ?
//

- (BOOL) isEmpty
{
  return (_length == 0);
}

//
// Return the length of the string
//
// @return the length of the string
//

- (unsigned long) length
{
  return _length;
}

//
// Return the pointer to a c-string notation of the string
//
// @return the pointer to the c-string
//

- (const char *) cstring
{
  _string[_length] = '\0';
  
  return _string;
}

//
// Return the last occurred error
//
// @return the last error (or 0)
//

- (int) error
{
  return _error;
}


//// DTextable protocol methods

//
// Convert to a text string object
//
// @return a (new) text string object
//

- (DText *) toText
{
  return [self copy];
}


//// DDatable protocol methods

//
// Convert to a data string
//
// @return a (new) data string object
//

- (DData *) toData
{
  return [[DData alloc] init :_string :_length];
}


//// DTextReadable protocol methods

//
// Read text string (all available text)
//
// @return a (new) text string
//

- (DText *) readText
{
  DText *text = [DText new];
  
  _error = 0;
  
  if (_pntr < _length)
  {
    [text set :_string :_pntr :_length-1];
    
    _pntr = _length;
  }
  else
  {
    _error = ENODATA;
  }
  
  return text;
}

//
// Read text string 
//
// @param length   the length of the text to be read
//
// @return a (new) text string
//

- (DText *) readText :(long) length
{
  DText *text = [[[DText alloc] init] size :length];

  _error = 0;
  
  length = (_pntr + length > _length) ? (_length - _pntr) : length;

  if (length > 0)
  {
    [text set :_string :_pntr :_pntr+length-1];
    
    _pntr += length;
  }
  else
    _error = ENODATA;
    
  return text;
}

//
// Read a line of text (till Eof or \n, not including in text string)
//
// @return a (new) text string
//

- (DText *) readLine
{
  DText *text = [[[DText alloc] init] extra :20];

  _error = (_pntr >= _length) ? ENODATA : 0;
  
  while ((_pntr < _length) && (_string[_pntr] != '\n'))
  {
    [text push :_string[_pntr]];

    _pntr++;
  }
  
  if (_pntr < _length) _pntr++;
  
  return text; 
}

//
// Read character
//
// @return the character read
//

- (char) readChar
{
  if (_pntr + sizeof(char)  <= _length)
  {
    _error = 0;
    
    return (char) _string[_pntr++];
  }
  else
  {
    _error = ENODATA;
    
    return EOS;
  }
}

//
// Tell the current position in the text string
//
// @return the current position
//

- (unsigned long) tell
{
  _error = 0;
  
  return _pntr;
}

//
// Move the current position in the text string
//
// @param offset   the offset from the origin (in bytes)
// @param origin   the origin for the offset (0=begin, 1=current, 2=end)
//
// @return success
//

- (BOOL) seek :(unsigned long) offset :(int) origin
{
  BOOL ok = YES;
  
  switch (origin)
  {
    case 0:
      if (offset > _length)
      {
        _pntr = _length;
        ok    = NO;
      }
      else
        _pntr = offset;
      break;
      
    case 1:
      if (_pntr + offset > _length)
      {
        _pntr = _length;
        ok    = NO;
      }
      else        
        _pntr = _pntr + offset;
      break;
      
    case 2:
      if (_length - offset < 0)
      {
        _pntr = 0;
        ok    = NO;
      }
      else
        _pntr = _length - offset;
      break;

    default:
      WARNING(DW_INVALID_ARG, "origin");
      break;
  }

  _error = (ok ? 0 : ERANGE);
  
  return ok;
}

//
// Skip a number of positions
//
// @param offset   the number of bytes to skip
//
// @return success
//

- (BOOL) skip :(unsigned long) offset
{
  if (_pntr + offset > _length)
  {
    _pntr = _length;

    _error = ERANGE;
    
    return NO;
  }
  else
  {
    _pntr += offset;

    _error = 0;
    
    return YES;
  }
}

//
// Check if the end of the string is reached
//
// @return is it?
//

- (BOOL) isEof
{
  _error = 0;
  
  return (_pntr == _length);
}



//// TextScannable protocol methods

//
// Scan for a text string until a separator character. The separator character is 
// is not part of the returned string and is skipped in the text object.
//
// @param separator  the separator character
//
// @return a (new) text string (or nil if the separator is not found)
//

- (DText *) scanText :(char) separator
{
  DText *text = nil;
  long   temp = _pntr;

  while ((temp < _length) && (_string[temp] != separator))
  {
    temp++;
  }

  if (temp < _length)
  {
    text = [DText new];

    [text set :[self cstring] :_pntr :temp-1];

    _pntr = temp + 1;
  }

  return text;
}

//
// Match a string case sensitive; if the string matched, the current position in
// the readable is moved to the location after the matched string.
//
// @param cstring   the c-string to be matched
//
// @return does the string match ?
//

- (BOOL) cmatch :(const char *) cstring
{
  BOOL matched = NO;

  if ((cstring == NULL) || (*cstring == EOS))
  {
    WARNING(DW_INVALID_ARG, "cstring");
  }
  else
  {
    long length = strlen(cstring);
    long temp   = _pntr;
    long index  = 0;

    while ((temp < _length) && (index < length) && (_string[temp] == cstring[index]))
    {
      temp++;
      index++;
    }

    if (index == length)
    {
      matched = YES;
      _pntr   = temp;
    }
  }

  return matched;
}

//
// Match a string case insensitive; if the string matched, the current position in
// the readable is moved to the location after the matched string.
//
// @param cstring   the c-string to be matched
//
// @return does the string match ?
//

- (BOOL) imatch :(const char *) cstring
{
  BOOL matched = NO;

  if ((cstring == NULL) || (*cstring == EOS))
  {
    WARNING(DW_INVALID_ARG, "cstring");
  }
  else
  {
    long length = strlen(cstring);
    long temp   = _pntr;
    long index  = 0;

    while ((temp < _length) && (index < length) && (tolower(_string[temp]) == tolower(cstring[index])))
    {
      temp++;
      index++;
    }

    if (index == length)
    {
      matched = YES;
      _pntr   = temp;
    }
  }

  return matched;
}

//
// Scan for a (decimal) integer value
//
// @param wrong   the value returned if there is no int to scan
//
// @return the integer read (or the wrong value)
//

- (int) scanInt :(int) wrong
{
  int  result = 0;
  long temp   = _pntr;
  BOOL neg    = NO;

  if ((temp < _length) && (_string[temp] == '-'))
  {
    neg = YES;

    temp++;
  }

  while ((temp < _length) && (isdigit(_string[temp])))
  {
    result = result * 10 + (int) (_string[temp] - '0');

    temp++;
  }

  if (temp != _pntr)
  {
    _pntr = temp;

    if (neg) 
      result = -result;
  }
  else
  {
    result = wrong;
  }

  return result;
}

//
// Skip characters
//
// @param ch    the character to be skipped
//
// @return the number of characters skipped
//

- (int) skipChar :(char) ch
{
  int count = 0;

  while ((_pntr < _length) && (_string[_pntr] == ch))
  {
    _pntr++;
    count++;
  }

  return count;
}

//
// Skip whitespace
//
// @return the number of characters skipped
//

- (int) skipWhiteSpace
{
  int count = 0;

  while ((_pntr < _length) && (isspace(_string[_pntr])))
  {
    _pntr++;
    count++;
  }

  return count;
}


//// TextWritable protocol methods

//
// Write text string 
//
// @param text     the text to be written
//
// @return success
//

- (BOOL) writeText :(const char *) text;
{
  unsigned long length = (text != NULL) ? strlen(text) : 0;
  
  if (_pntr + length > _length)
  {
    _length = _pntr + length;

    [self size :_length];
  }
  memcpy(_string + _pntr, text, length);

  _pntr+= length;

  _error = 0;
  
  return YES;
}

//
// Write line (appending a '\n')
//
// @param text     the text to be written as line
//
// @return success
//

- (BOOL) writeLine :(const char *) text
{
  _error = 0;
  
  [self writeText :text];
  [self writeChar :'\n'];

  return YES;
}

//
// Write character
//
// @param ch       the character to be written
//
// @return success
//

- (BOOL) writeChar :(char) ch;
{
  _error = 0;
  
  if (_pntr + sizeof(ch) > _length)
  {
    _length = _pntr + sizeof(ch);

    [self size :_length];
  }
  _string[_pntr++] = ch;

  return YES;
}


//// DParsable methods

//
// Parse a string for a DText string
// 
// @param cstr     the string to be parsed (moved to the first non-parsable char)
// 
// @return the result (0, ENODATA)
// 

- (int) fromString :(char **) cstr
{
  _length = (*cstr != NULL) ? strlen(*cstr) : 0;

  [self size :_length];

  memcpy(_string, *cstr, _length);
  
  (*cstr) += _length;
  
  return (_length == 0) ? ENODATA : 0;
}


//// String manipulation methods

//
// Append a c-string to the string
//
// @param cstring  the string to be appended
//
// @return the string object
//

- (DText *) append :(const char *) cstring
{
  int length = (cstring != NULL) ? strlen(cstring) : 0;

  if (length > 0)
  {
    [self size :(_length + length)];

    memcpy(_string + _length, cstring, length);
    
    _length += length;
  }
  
  return self;
}

//
// Prepend a c-string in front of the string
//
// @param cstring  the string to be prepended
//
// @return the string object
//

- (DText *) prepend :(const char *) cstring
{
  int length = (cstring != NULL) ? strlen(cstring) : 0;

  if (length > 0)
  {
    [self size :(_length + length)];

    memmove(_string + length, _string, _length);
    
    memcpy(_string, cstring, length);

    _length += length;
  }

  return self;
}

//
// Push one character at the end of the string
// 
// @param ch       the character
//
// @return the object
//

- (DText *) push :(char) ch
{
  [self size :(_length + 1)];

  _string[_length++] = ch;

  return self;
}

//
// Pop the last character from the string
//
// @return the character  (or \0)
//

- (char) pop
{
  char ch = EOS;
  
  if (_length > 0)
  {
    ch = _string[--_length];
  }

  return ch;
}

//    
// Multiply the string a number of times
//
// @param times    the number of times the string must be multiplied
//
// @return the string object
//

- (DText *) multiply :(unsigned) times
{
  long length = _length;
  long dst    = _length;
  
  _length *= times;

  if (_length > 0)
  {
    [self size :_length];

    while (times > 1)
    {
      memcpy(_string + dst, _string, length);

      dst += length;
      
      times--;
    }
  }
  
  return self;
}

//
// Capitalize the first character of the string
//
// @return the string object
//

- (DText *) capitalize
{
  if (_length > 0)
  {
    _string[0] = toupper(_string[0]);
  }

  return self;
}

//
// Capitalize the first character of every word, replaces repeated whitespace
// with one space and strips leading and trailing spaces.
//
// @return the string object
//

- (DText *) capwords
{
  char *src = _string;
  char *dst = _string;
  int   len = _length;
  
  _length = 0;
 
  while (len > 0)
  {
    // skip leading spaces
    while ((len > 0) && (isspace(*src)))
    {
      src++; len--;
    }

    if (len > 0)
    {
      if (_length != 0)
      {
        *dst++ = ' '; _length++;
      }
    
      // capitalize the first character
      *dst++ = toupper(*src++); 
    
      _length++; len--;
   
      // copy the word
      while ((len > 0) && (!isspace(*src)))
      {
        *dst++ = *src++;

        _length++; len--;
      }
    }
  }
      
  return self;
}

//
// Expand all tabs with tabsize spaces
//
// @param tabsize  the size of the tab in spaces
//
// @return the string object
//

- (DText *) expandtabs :(unsigned) tabsize
{
  if (_length > 0) 
  {
    long  len   = _length;
    char *copy  = (char *) objc_malloc(_length);
    char *src   = copy;
    long  dst   = 0;
    long  extra = 0;
    
    memcpy(copy, _string, _length);
     
    while (len > 0)
    {
      if (*src == '\t')
      {
        if (tabsize > 0)
        {
          extra = tabsize - dst % tabsize;
        }
            
        _length += (extra - 1);

        if (_length >= _size)
        {
          [self size :(_length + 8 * extra)];
        }

        memset(_string + dst, ' ', extra);

        dst += extra;
        src ++;
      }
      else
      {
        _string[dst++] = *src++;
      }
      
      len--;
    }

    objc_free(copy);
  }
  
  return self;
}

//
// Convert all uppercase characters in the string to lowercase
//
// @return the string object
//

- (DText *) lower
{
  long  i;
  char *src = _string;
  
  for (i = 0; i < _length; i++)
  {
    *src = tolower(*src);

    src++;
  }

  return self;
}

//
// Convert all lowercase characters in the string to uppercase
//
// @return the string object
//

- (DText *) upper
{
  long  i;
  char *src = _string;
  
  for (i = 0; i < _length; i++)
  {
    *src = toupper(*src);

    src++;
  }

  return self;
}

//
// Convert all lowercase to uppercase and uppercase to lowercase
//
// @return the string object
//

- (DText *) swapcase
{
  long  i;
  char *src = _string;
  
  for (i = 0; i < _length; i++)
  {
    if (islower(*src))
    {
      *src = toupper(*src);
    }
    else if (isupper(*src))
    {
      *src = tolower(*src);
    }
    src++;
  }

  return self;
}

//
// Strip leading spaces from the string
//
// @return the string object
//

- (DText *) lstrip
{
  char *src = _string;

  while ((_length > 0) && (isspace(*src)))
  {
    src++;

    _length--;
  }

  memmove(_string, src, _length);
  
  return self;
}

//
// Strip tailing spaces from the string
//
// @return the string object
//

- (DText *) rstrip
{
  char *src = _string + _length - 1;

  while ((_length > 0) && (isspace(*src)))
  {
    src--;
    
    _length--;
  }
  
  return self;
}

//
// Strip both leading and tailing spaces from the string
//
// @return the string object
//

- (DText *) strip
{
  [self rstrip];
  [self lstrip];

  return self;
}

//
// Align the string to the left
//
// @param width    the length of the resulting string
//
// @return the string object
//

- (DText *) ljust :(unsigned long) width
{
  if (width > _length)
  {
    [self size :width];

    memset(_string + _length, ' ', (width - _length));

    _length = width;
  }
  
  return self;
}

//
// Align the string to the right
//
// @param width    the length of the resulting string
//
// @return the string object
//

- (DText *) rjust :(unsigned long) width
{
  if (width > _length)
  {
    [self size :width];

    memmove(_string + (width - _length), _string, _length);

    memset(_string, ' ', (width - _length));

    _length = width;
  }
  
  return self;
}

//
// Center the string
//
// @param width    the length of the resulting string
//
// @return the string object
//

- (DText *) center :(unsigned long) width
{
  if (width > _length)
  {
    long right = (width - _length) / 2;
    long left  = width - _length - right;

    [self size :width];

    memmove(_string + left, _string, _length);
    
    memset(_string, ' ', left);
    memset(_string + (left + _length), ' ', right);

    _length = width;
  }
  
  return self;
}

//
// Pad a string to the left with zeros 
//
// @param width    the length of the resulting string
//
// @return the string object
//

- (DText *) zfill  :(unsigned long) width
{
  if (width > _length)
  {
    [self size :width];

    memmove(_string + (width - _length), _string, _length);

    memset(_string, '0', (width - _length));

    _length = width;
  }
  
  return self;
}

//
// Insert linefeeds in a string to create a column
// 
// @param width    the width of the column
// 
// @return the object
// 

- (DText *) column :(int) width
{
  if (width > 0)
  {
    long index = 0;
  
    while (index < _length)
    {
      long len  = 0;
      long word = -1;
      
      while ((index < _length) && (_string[index] != '\n') && (len < width))
      {
        if ((!isspace(_string[index])) && ((index+1) < _length) && (isspace(_string[index+1])))
        {
          word = index;
        }
        
        index++;
        len++;
      }
      
      if (_string[index] == '\n')
      {
        index++;
      }
      else if (index >= _length)
      {
        [self append :"\n"];
        index++;
      }
      else // (len >= width
      {
        if (word >= 0)
        {
          long next = word + 1;
          
          while ((next < _length) && (isspace(_string[next])))
          {
            next++;
          }
          if (_string[next] == '\n')
          {
            next++;
          }
          
          [self insert :word+1 :next-1 :'\n' :1];
          
          index = word + 2;
        }
        else
        {
          [self insert :index :index-1 :'\n' :1];
          
          index++;
        }
      }
    }
  }
  else
    WARNING(DW_INVALID_ARG, "width");
  
  return self;
}

//// Comparable protocol methods

//
// Compare the string with another string
//
// @param other    the string that should be compared
//
// @return the compare result (-1, 0, 1)
//

- (int) compare :(DText *) other
{
  char *c1       = _string;
  long l1        = _length;
  const char *c2 = [other cstring];
  long l2        =  (other != NULL) ? [other length] : 0;
  
  while ((l1 > 0) && (l2 > 0) && (*c1 == *c2))
  {
    c1++; c2++; l1--; l2--;
  }
  
  if ((l1 > 0) && (l2 > 0))
  {
    return (*c1 > *c2) ? 1 : -1;
  }
  else if (l1 > 0)
  {
    return 1;
  }
  else if (l2 > 0)
  {
    return -1;
  }
  
  return 0;
}


//// Comparison methods

//
// Compare case sensitive the string with a c-string
//
// @param other    the c-string that should be tested
//
// @return the test result (-1, 0, 1)
//

- (int) ccompare :(const char *) other
{
  char *c1       = _string;
  long l1        = _length;
  const char *c2 =  other;
  long l2        =  (other != NULL) ? strlen(other) : 0;
  
  while ((l1 > 0) && (l2 > 0) && (*c1 == *c2))
  {
    c1++; c2++; l1--; l2--;
  }
  
  if ((l1 > 0) && (l2 > 0))
  {
    return (*c1 > *c2) ? 1 : -1;
  }
  else if (l1 > 0)
  {
    return 1;
  }
  else if (l2 > 0)
  {
    return -1;
  }
  
  return 0;
}

//
// Compare case insensitive the string with a c-string
//
// @param other    the c-string that should be compared
//
// @return the compare result (-1, 0, 1)
//

- (int) icompare :(const char *) other
{
  char *c1       = _string;
  long l1        = _length;
  const char *c2 =  other;
  long l2        =  (other != NULL) ? strlen(other) : 0;
  
  while ((l1 > 0) && (l2 > 0) && (tolower(*c1) == tolower(*c2)))
  {
    c1++; c2++; l1--; l2--;
  }
  
  if ((l1 > 0) && (l2 > 0))
  {
    return (tolower(*c1) > tolower(*c2)) ? 1 : -1;
  }
  else if (l1 > 0)
  {
    return 1;
  }
  else if (l2 > 0)
  {
    return -1;
  }
  
  return 0;
}

//
// Compare case sensitive the string with the first number of characters 
// of a c-string
//
// @param other    the c-string that should be tested
// @param length   the length of the string to be tested
//
// @return the test result (-1, 0, 1)
//

- (int) ccompare :(const char *) other :(unsigned long) length
{
  char *c1       = _string;
  long l1        = _length;
  const char *c2 =  other;
  long l2        =  (other != NULL) ? strlen(other) : 0;
  
  if (l2 > length)
    l2 = length;

  if (l1 > length)
    l1 = length;
  
  while ((l1 > 0) && (l2 > 0) && (*c1 == *c2))
  {
    c1++; c2++; l1--; l2--;
  }
  
  if ((l1 > 0) && (l2 > 0))
  {
    return (*c1 > *c2) ? 1 : -1;
  }
  else if (l1 > 0)
  {
    return 1;
  }
  else if (l2 > 0)
  {
    return -1;
  }
  
  return 0;
}

//
// Compare case insensitive the string with the first number of characters
// of a c-string
//
// @param other    the c-string that should be compared
// @param length   the length of the string to be tested
//
// @return the compare result (-1, 0, 1)
//

- (int) icompare :(const char *) other :(unsigned long) length
{
  char *c1       = _string;
  long l1        = _length;
  const char *c2 =  other;
  long l2        =  (other != NULL) ? strlen(other) : 0;
  
  if (l2 > length)
    l2 = length;
  
  if (l1 > length)
    l1 = length;
  
  while ((l1 > 0) && (l2 > 0) && (tolower(*c1) == tolower(*c2)))
  {
    c1++; c2++; l1--; l2--;
  }
  
  if ((l1 > 0) && (l2 > 0))
  {
    return (tolower(*c1) > tolower(*c2)) ? 1 : -1;
  }
  else if (l1 > 0)
  {
    return 1;
  }
  else if (l2 > 0)
  {
    return -1;
  }
  
  return 0;
}


//// Search and replace methods

//
// Count the number of occurrences of the c-string in the string
//
// @param search   the c-string that should be counted
// @param from     the start index 
// @param to       the end index (to >= from)
//
// @return the number of occurrences
//

- (unsigned long) count :(const char *) search :(long) from :(long) to
{
  unsigned long cnt = 0;
  
  if (search != NULL)
  {
    int length = strlen(search);
  
    from = index2offset(self, from);
    to   = index2offset(self, to  );

    while (from <= to)
    {
      if (memcmp(_string + from, search, length) == 0)
      {
        cnt++;

        from += length;
      }
      else
      {
        from++;
      }
    }
  }
      
  return cnt;
}

//
// Return the first index where the search c-string is found
//
// @param search   the c-string that should be searched
// @param from     the start index
// @param to       the end index (to >= from)
//
// @return the first index or -1 = not found
//

- (long) index  :(const char *) search :(long) from :(long) to
{
  if (search != NULL)
  { 
    int length = strlen(search);
    
    from = index2offset(self, from);
    to   = index2offset(self, to  );
    
    to -= length;
    
    while (from <= to)
    {
      if (memcmp(_string + from, search, length) == 0)
      {
        return from;
      }
      from++;
    }
  }

  return -1;
}

//
// Return the last index where the search c-string is found
//
// @param search   the c-string that should be searched
// @param from     the start index
// @param to       the end index (to >= from)
//
// @return the last index or -1 = not found
//

- (long) rindex :(const char *) search :(long) from :(long) to
{
  if (search != NULL)
  {
    int length = strlen(search);
    
    from = index2offset(self, from);
    to   = index2offset(self, to  );

    to -= length;

    while (to >= from)
    {
      if (memcmp(_string + to, search, length) == 0)
      {
        return to;
      }
      to--;
    }
  }
  
  return -1;
}

//
// Replace in the string the occurrences of old with new, max times
//
// @param old      the c-string that is searched
// @param new      the c-string that is inserted
// @param max      the maximum number of replaces (-1 = all)
//
// @return the string object
//

- (DText *) replace :(const char *) old :(const char *) new :(long) max
{
  if ((old != NULL) && (_length > 0))
  {
    long cnt       = 0;
    long oldLength = strlen(old);
    long newLength = (new != NULL) ? strlen(new) : 0;
    long delta     = 0;
    char *copy     = (char *) objc_malloc(_length);
    char *src      = copy;
    long  len      = _length;
    long  dst      = 0;
    
    delta = newLength - oldLength;
    
    memcpy(copy, _string, _length);

    while (len > 0) 
    {
      if (((cnt < max) || (max == -1)) && (memcmp(src, old, oldLength) == 0))
      {
        cnt++;
        
        _length += delta;
        if (_length >= _size)
        {
          [self size :(_length + 16 * delta)];
        }

        if (newLength > 0)
        {
          memcpy(_string + dst, new, newLength);

          dst += newLength;
        }
        src += oldLength;
        
        len -= oldLength;
      }
      else
      {
        _string[dst++] = *src++;

        len--;
      }
    }

    objc_free(copy);
  }
  
  return self;
}

@end

/*===========================================================================*/

